﻿using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;

namespace Sidvall.Security
{
    public class IdentityValidatorCollection : System.Collections.ObjectModel.Collection<IIdentityValidator>
    {
        #region Public Members

        public IdentityValidationResultCachePolicy ValidationResultCachePolicy { get; set; }

        #region AddRange

        public void AddRange(IEnumerable<IIdentityValidator> items)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if (item != null)
                    Add(item);
            }
        }

        #endregion
        #region GetValidationResultAsync

        public async Task<IdentityValidationResult> GetValidationResultAsync(IPrincipal principal)
        {
            return await GetValidationResultAsync(principal?.Identity);
        }
        public async Task<IdentityValidationResult> GetValidationResultAsync(IIdentity identity)
        {
            if (this.Count == 0)
            {
                return new IdentityValidationResult()
                {
                    IdentityStatus = IdentityStatus.Valid,
                    Claims = new Sidvall.Security.ClaimCollection(),
                    ObjectParameters = new Sidvall.Data.ObjectParameterCollection(),
                };
            }
            var name = Sidvall.SystemContext.Current.PrincipalManager.GetIdentityName(identity);
            if (string.IsNullOrWhiteSpace(name))
            {
                return await GetValidationResultAsync(identity, this);
            }
            else if (this.ValidationResultCachePolicy != Sidvall.Security.IdentityValidationResultCachePolicy.None)
            {
                if (!this.ValidationResultIndex.ContainsKey(name))
                {
                    var item = await GetValidationResultAsync(identity, this);
                    if ((item.IdentityStatus == IdentityStatus.Valid) || (this.ValidationResultCachePolicy == Sidvall.Security.IdentityValidationResultCachePolicy.All))
                    {
                        lock (_Mutex)
                        {
                            if (!this.ValidationResultIndex.ContainsKey(name))
                            {
                                this.ValidationResultIndex.Add(name, item);
                            }
                        }
                    }
                    return item;
                }
                return this.ValidationResultIndex[name];
            }
            else
            {
                return await GetValidationResultAsync(identity, this);
            }
        }
        private static async Task<IdentityValidationResult> GetValidationResultAsync(IIdentity identity, IdentityValidatorCollection userValidators)
        {
            var results = IdentityValidationResultCollection.Create(userValidators.Count);
            var tasks = new List<Task>();
            for (int i = 0; i < userValidators.Count; i++)
            {
                var userValidator = userValidators[i];
                tasks.Add(ProcessAsync(userValidator, identity, results, i));
            }
            await Task.WhenAll(tasks);
            return results.GetResult();
        }

        #endregion

        #endregion
        #region Private Members

        private readonly object _Mutex = new object();

        #region ValidationResultIndex

        private System.Lazy<Dictionary<string, IdentityValidationResult>> _ValidationResultIndex = new System.Lazy<Dictionary<string, IdentityValidationResult>>(GetValidationResultIndex);
        private Dictionary<string, IdentityValidationResult> ValidationResultIndex
        {
            get
            {
                return _ValidationResultIndex.Value;
            }
        }

        #endregion

        #region GetValidationResultIndex

        private static Dictionary<string, IdentityValidationResult> GetValidationResultIndex()
        {
            return new Dictionary<string, IdentityValidationResult>(System.StringComparer.OrdinalIgnoreCase);
        }

        #endregion
        #region ProcessAsync

        private static async Task ProcessAsync(IIdentityValidator item, IIdentity identity, IdentityValidationResultCollection results, int index)
        {
            results[index] = await item.ValidateAsync(identity);
        }

        #endregion

        #endregion
        #region Constructors

        public IdentityValidatorCollection()
        {
        }

        #endregion
    }
}
